﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;
using System.Data.Objects;
using Slam.Configuration;
using Slam;
using Microsoft.SharePoint;
using System.Web;
using System.Data.EntityClient;
using SlamCms.SharePoint.Data;
using Microsoft.SharePoint.Client.Services;
using Microsoft.SharePoint.WebControls;
using SlamCms.SharePoint.Core;
using SlamCms.Configuration;
using SlamCms.Common;
using UserQueries = SlamCms.SharePoint.UserManagement.Data.Queries;
using Slam.Logging;
using System.Threading;

namespace SlamCms.SharePoint.UserManagement
{
	[ServiceContract(Namespace = "")]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
	[BasicHttpBindingServiceMetadataExchangeEndpoint]
	[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
	public class UserManagementService
	{
		private static object _lock = new object();

		[WebInvoke(Method = "POST",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "GetPublishers?keywords={keywords}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public DataTablesServiceResponse GetPublishers(string keywords)
		{
			int echo = int.Parse(HttpContext.Current.Request["sEcho"]);
			int displayStart = int.Parse(HttpContext.Current.Request["iDisplayStart"]);
			int displayLength = int.Parse(HttpContext.Current.Request["iDisplayLength"]);

			string filterUser = HttpContext.Current.Request["sSearch_0"];
			string filterPermission = HttpContext.Current.Request["sSearch_1"];
			string filterTopic = HttpContext.Current.Request["sSearch_2"];

			string[] keys = keywords.Split(';');
			int totalContentItems = 0;


			var groups = (from g in SlamCmsContext.Data.Groups.Where(g => keys.Any(k => g.Name.Contains(k)) && g.SiteCollectionGroupId != 0)
						  select g).ToList();

			var tagOwnerUsers = (from u in SlamCmsContext.Data.Users
								 where u.OwnedTags.Count > 0
								 select u).ToList();


			var groupsUsers = (from u in SlamCmsContext.Data.Users.Where(u => groups.Any(g => u.Groups.Contains(g)))
							   select u).ToList();


			var preResult = from u in groupsUsers.Union(tagOwnerUsers).OrderByDescending(u => u.Name)
							select u;

			if (!String.IsNullOrEmpty(filterUser))
				preResult = from u in preResult
							where u.Name.ToUpper().Contains(filterUser.ToUpper())
							select u;

			if (!String.IsNullOrEmpty(filterPermission))
				preResult = from u in preResult
							where u.Groups.Any(g => g.Name.ToUpper().Contains(filterPermission.ToUpper()))
							select u;

			if (!String.IsNullOrEmpty(filterTopic))
				preResult = from u in preResult
							where u.OwnedTags.Any(t => t.DisplayName.ToUpper().Contains(filterTopic.ToUpper()))
							select u;

			totalContentItems = preResult.Count();

			var result = from u in preResult.Skip(displayStart).Take(displayLength)
						 select new[] { keywords, u.UserId.ToString(), u.Name + String.Format(" ({0})", u.UserName), ConvertGroupsToString(u.Groups, keys), ConvertTagsToString(u.OwnedTags) };


			return new DataTablesServiceResponse
			{
				sEcho = echo,
				iTotalRecords = totalContentItems,
				iTotalDisplayRecords = totalContentItems,
				aaData = (result != null ? result : new List<string[]>())
			};
		}


		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "GetUsers?searchTerm={searchTerm}",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public List<SlamCms.SharePoint.Tagging.AutoCompleteItem> GetUsers(string searchTerm)
		{
			var users = SlamCmsContext.Data.Users.Where(u => u.Name.ToUpper().Contains(searchTerm.ToUpper()) || u.UserName.ToUpper().Contains(searchTerm.ToUpper()));

			return users.Select(u => new SlamCms.SharePoint.Tagging.AutoCompleteItem
			{
				id = u.UserId.ToString(),
				value = u.Name
			}).ToList();

		}

		[WebInvoke(Method = "GET",
		ResponseFormat = WebMessageFormat.Json,
		UriTemplate = "Synchronize",
		BodyStyle = WebMessageBodyStyle.Bare)]
		[OperationContract]
		public void Synchronize()
		{
			lock (_lock)
			{
				SPSite currentSiteCollection = SPControl.GetContextSite(HttpContext.Current);
				SPSecurity.RunWithElevatedPrivileges(delegate()
				{
					ILogger logger = ComponentService.GetInstance<ILogger>();
					logger.LogMessage("Information", "UserGroupSynchronizer - Starting Sync");
					using (SPSite siteCollection = new SPSite(currentSiteCollection.ID))
					{
						if (siteCollection != null)
						{

							//Determine schema and connection string using SLAM Configuration
							string dataSchema = "dbo";
							string connectionString = "";
							IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
							DataMapping currentDataMapping = slamConfiguration.DataMapping;
							string siteCollectionUrl = siteCollection.ServerRelativeUrl.TrimStart('/');
							if (!String.IsNullOrEmpty(siteCollectionUrl) && slamConfiguration.DataMappings.ContainsKey(siteCollectionUrl))
							{
								currentDataMapping = slamConfiguration.DataMappings[siteCollectionUrl];
							}
							if (slamConfiguration is XmlConfigurationManager)
							{
								dataSchema = ((XmlSqlDataMapping)currentDataMapping).GetDataSchema();
								connectionString = ((XmlSqlDataMapping)currentDataMapping).GetConnectionString();
							}

							if (String.IsNullOrEmpty(connectionString))
							{
								connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
							}
							else
							{
								connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(connectionString);
							}

							string modelName = "Data.SlamCms";
							EntityConnectionStringBuilder entityConnection = new EntityConnectionStringBuilder();
							entityConnection.Provider = "System.Data.SqlClient";
							if (!connectionString.Contains("Persist Security Info"))
							{
								connectionString = connectionString.TrimEnd(';') + ";Persist Security Info=True;";
							}
							entityConnection.ProviderConnectionString = connectionString;
							entityConnection.Metadata = String.Format("res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl", modelName);

							//Create object context and set schema
							Entities context = new Entities(entityConnection.ToString());
							context.SetStoreSchema(modelName, dataSchema);

							//Ensure tables exists
							if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifyTableExists, dataSchema, "Group")).FirstOrDefault() == 0)
							{
								context.ExecuteStoreCommand(String.Format(UserQueries.CreateGroupTable, dataSchema));
							}

							if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifyTableExists, dataSchema, "GroupSite")).FirstOrDefault() == 0)
							{
								context.ExecuteStoreCommand(String.Format(UserQueries.CreateGroupSiteTable, dataSchema));
							}

							if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifyTableExists, dataSchema, "UserGroup")).FirstOrDefault() == 0)
							{
								context.ExecuteStoreCommand(String.Format(UserQueries.CreateUserGroupTable, dataSchema));
							}


							//Add synch column and trigger to connector tables
							context.ExecuteStoreCommand(String.Format(UserQueries.AddIsSynchedColumn, dataSchema, "GroupSite"));
							if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifySynchUpdateTriggerExists, dataSchema, "GroupSite")).FirstOrDefault() == 0)
							{
								context.ExecuteStoreCommand(String.Format(UserQueries.AddSynchUpdateTrigger, dataSchema, "GroupSite", "GroupID", "SiteID"));
							}

							context.ExecuteStoreCommand(String.Format(UserQueries.AddIsSynchedColumn, dataSchema, "UserGroup"));
							if (context.ExecuteStoreQuery<int>(String.Format(UserQueries.VerifySynchUpdateTriggerExists, dataSchema, "UserGroup")).FirstOrDefault() == 0)
							{
								context.ExecuteStoreCommand(String.Format(UserQueries.AddSynchUpdateTrigger, dataSchema, "UserGroup", "UserID", "GroupID"));
							}

							try
							{
								SiteCollection siteCollectionItem = (from item in context.SiteCollections
																	 where item.SiteCollectionID == siteCollection.ID
																	 select item).FirstOrDefault();

								if (siteCollectionItem == null)
								{
									SiteCollection newSiteCollectionItem = new SiteCollection();
									newSiteCollectionItem.SiteCollectionID = siteCollection.ID;
									newSiteCollectionItem.ServerRelativeUrl = siteCollection.ServerRelativeUrl;
									context.AddToSiteCollections(newSiteCollectionItem);
									logger.LogMessage("Information", "UserGroupSynchronizer - Adding SiteCollection: " + siteCollection.ServerRelativeUrl);
									context.SaveChanges(true);

									siteCollectionItem = newSiteCollectionItem;
								}

								Dictionary<int, int> syncedSiteCollectionGroupIds = new Dictionary<int, int>();

								string fullPermissionMask = Convert.ToString((long)SPBasePermissions.FullMask);


								var siteCollectionSites = (from s in context.Sites.Include("Groups")
														   where s.SiteCollection.SiteCollectionID == siteCollection.ID
														   select s).ToList();

								foreach (var siteCollectionSite in siteCollectionSites)
								{
									foreach (var group in siteCollectionSite.Groups)
									{
										if (!syncedSiteCollectionGroupIds.ContainsKey(group.SiteCollectionGroupID))
											syncedSiteCollectionGroupIds.Add(group.SiteCollectionGroupID, group.GroupID);
									}
								}

								//Add Site Collection Admin group
								Group adminGroup = null;
								if (syncedSiteCollectionGroupIds.ContainsKey(0))
								{
									int groupId = syncedSiteCollectionGroupIds[0];
									adminGroup = context.Groups.Where(g => g.GroupID == groupId).FirstOrDefault();
								}

								if (adminGroup == null)
								{
									adminGroup = new Group();
									adminGroup.SiteCollectionGroupID = 0;
									adminGroup.Name = fullPermissionMask;
									adminGroup.Add = true;
									adminGroup.Approve = true;
									adminGroup.Delete = true;
									adminGroup.Edit = true;
									adminGroup.View = true;
									context.AddToGroups(adminGroup);
									logger.LogMessage("Information", "UserGroupSynchronizer - Adding User: " + fullPermissionMask);
									context.SaveChanges(true);
								}

								if (!syncedSiteCollectionGroupIds.ContainsKey(adminGroup.SiteCollectionGroupID))
									syncedSiteCollectionGroupIds.Add(adminGroup.SiteCollectionGroupID, adminGroup.GroupID);

								using (SPWeb rootWeb = siteCollection.RootWeb)
								{
									foreach (SPUser user in rootWeb.SiteAdministrators)
									{
										string userName = user.LoginName.Contains("|") ? user.LoginName.Split('|').Last() : user.LoginName;
										User userItem = context.Users.Where(u => u.UserName == userName).FirstOrDefault();
										if (userItem == null)
										{
											userItem = new User();
											userItem.UserName = userName;
											userItem.Name = user.Name;
											userItem.Groups.Add(adminGroup);
											userItem.Email = user.Email;
											userItem.Language = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
											context.AddToUsers(userItem);
											logger.LogMessage("Information", "UserGroupSynchronizer - Adding User: " + userName);
											context.SaveChanges(true);
										}
										else
										{
											if (String.IsNullOrEmpty(userItem.Name))
												userItem.Name = user.Name;
											if (String.IsNullOrEmpty(userItem.Email))
												userItem.Email = user.Email;
											if (String.IsNullOrEmpty(userItem.Language))
												userItem.Language = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;

											userItem.Groups.Load();
											if (userItem.Groups.Where(g => g.GroupID == adminGroup.GroupID).FirstOrDefault() == null)
											{
												userItem.Groups.Add(adminGroup);
												context.SaveChanges(true);
											}
											else
											{
												context.ExecuteStoreCommand(String.Format(UserQueries.UpdateIsSynched, dataSchema, "UserGroup", "UserID", "GroupID", userItem.UserID.ToString(), adminGroup.GroupID.ToString()));
											}
										}
									}
								}

								foreach (SPWeb web in siteCollection.AllWebs)
								{
									Site siteItem = context.Sites.Where(s => s.SiteID == web.ID).FirstOrDefault();
									if (siteItem == null)
									{
										siteItem = new Site();
										siteItem.SiteID = web.ID;
										siteItem.Title = web.Title;
										siteItem.Name = web.Name;
										siteItem.Url = web.ServerRelativeUrl == "/" ? web.ServerRelativeUrl : web.ServerRelativeUrl.Substring(siteCollection.ServerRelativeUrl.Length);
										siteItem.SiteCollection = siteCollectionItem;
										context.AddToSites(siteItem);
										logger.LogMessage("Information", "UserGroupSynchronizer - Adding Site: " + siteItem.Name);
										context.SaveChanges(true);
									}

									if (adminGroup != null)
									{
										adminGroup.Sites.Load();
										if (adminGroup.Sites.Where(s => s.SiteID == web.ID).FirstOrDefault() == null)
										{
											adminGroup.Sites.Add(siteItem);
										}
										else
										{
											context.ExecuteStoreCommand(String.Format(UserQueries.UpdateIsSynched, dataSchema, "GroupSite", "GroupID", "SiteID", adminGroup.GroupID.ToString(), siteItem.SiteID.ToString()));
										}

										context.SaveChanges(true);
									}

									foreach (SPGroup group in web.Groups)
									{
										var roleAssignment = web.RoleAssignments.GetAssignmentByPrincipal(group);

										Group groupItem = null;

										if (syncedSiteCollectionGroupIds.ContainsKey(group.ID))
										{
											int groupId = syncedSiteCollectionGroupIds[group.ID];
											groupItem = context.Groups.Where(g => g.GroupID == groupId).FirstOrDefault();
										}
										else
										{
											siteItem.Groups.Load();
											groupItem = siteItem.Groups.Where(g => g.SiteCollectionGroupID == group.ID).FirstOrDefault();
										}

										if (groupItem == null)
										{
											groupItem = new Group();
											groupItem.SiteCollectionGroupID = group.ID;
											groupItem.Name = group.Name;
											foreach (var roleDefinitionBinding in roleAssignment.RoleDefinitionBindings.Cast<SPRoleDefinition>())
											{
												groupItem.Add = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.AddListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
												groupItem.Approve = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.ApproveItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
												groupItem.Delete = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.DeleteListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
												groupItem.Edit = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.EditListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
												groupItem.View = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.ViewListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											}
											groupItem.Sites.Add(siteItem);
											context.AddToGroups(groupItem);
											logger.LogMessage("Information", "UserGroupSynchronizer - Adding Group: " + groupItem.Name);
											context.SaveChanges(true);
										}
										else
										{
											groupItem.Name = group.Name;
											foreach (var roleDefinitionBinding in roleAssignment.RoleDefinitionBindings.Cast<SPRoleDefinition>())
											{
												groupItem.Add = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.AddListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
												groupItem.Approve = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.ApproveItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
												groupItem.Delete = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.DeleteListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
												groupItem.Edit = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.EditListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
												groupItem.View = roleDefinitionBinding.BasePermissions.ToString().Contains(SPBasePermissions.ViewListItems.ToString()) || roleDefinitionBinding.BasePermissions == SPBasePermissions.FullMask;
											}
											groupItem.Sites.Load();
											if (groupItem.Sites.Where(s => s.SiteID == web.ID).FirstOrDefault() == null)
											{
												groupItem.Sites.Add(siteItem);
											}
											else
											{
												context.ExecuteStoreCommand(String.Format(UserQueries.UpdateIsSynched, dataSchema, "GroupSite", "GroupID", "SiteID", groupItem.GroupID.ToString(), siteItem.SiteID.ToString()));
											}
											context.SaveChanges(true);
										}

										if (!syncedSiteCollectionGroupIds.ContainsKey(groupItem.SiteCollectionGroupID))
											syncedSiteCollectionGroupIds.Add(groupItem.SiteCollectionGroupID, groupItem.GroupID);

										foreach (SPUser user in group.Users)
										{
											string userName = user.LoginName.Contains("|") ? user.LoginName.Split('|').Last() : user.LoginName;
											User userItem = context.Users.Where(u => u.UserName == userName).FirstOrDefault();
											if (userItem == null)
											{
												userItem = new User();
												userItem.UserName = userName;
												userItem.Name = user.Name;
												userItem.Groups.Add(groupItem);
												userItem.Email = user.Email;
												userItem.Language = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
												context.AddToUsers(userItem);
												logger.LogMessage("Information", "UserGroupSynchronizer - Adding User: " + userName);
												context.SaveChanges(true);
											}
											else
											{
												userItem.Groups.Load();
												if (userItem.Groups.Where(g => g.GroupID == groupItem.GroupID).FirstOrDefault() == null)
												{
													userItem.Groups.Add(groupItem);
													context.SaveChanges(true);
												}
												else
												{
													context.ExecuteStoreCommand(String.Format(UserQueries.UpdateIsSynched, dataSchema, "UserGroup", "UserID", "GroupID", userItem.UserID.ToString(), groupItem.GroupID.ToString()));
												}
											}
										}
									}

									web.Dispose();
								}

							}
							catch (Exception ex)
							{	
								logger.LogMessage("Exception", "User Group Synchronizer Exception: " + ex.ToString());
							}
							siteCollection.Dispose();

							//Remove IsSynched column and synch trigger
							context.ExecuteStoreCommand(String.Format(UserQueries.ClearNonSynchedSiteCollectionUserGroupData, dataSchema, "UserGroup", siteCollection.ID.ToString()));
							context.ExecuteStoreCommand(String.Format(UserQueries.ClearNonSynchedSiteCollectionGroupSiteData, dataSchema, "GroupSite", siteCollection.ID.ToString()));
							context.ExecuteStoreCommand(String.Format(UserQueries.RemoveSynchUpdateTrigger, dataSchema, "GroupSite"));
							context.ExecuteStoreCommand(String.Format(UserQueries.RemoveSynchUpdateTrigger, dataSchema, "UserGroup"));
							context.ExecuteStoreCommand(String.Format(UserQueries.RemoveIsSynchedColumn, dataSchema, "GroupSite"));
							context.ExecuteStoreCommand(String.Format(UserQueries.RemoveIsSynchedColumn, dataSchema, "UserGroup"));

							SlamCmsContext.Data.Cache.InvalidateCore();
							SlamCmsContext.Data.Cache.InvalidateTags();
							SlamCmsContext.Data.Cache.InvalidateUsers();
							logger.LogMessage("Information", "UserGroupSynchronizer - Ending Sync");
						}
					}
				});
			}
		}


		private string ConvertGroupsToString(ICollection<SlamCms.Data.Group> groups, string[] keywords)
		{
			List<string> convertedGroups = new List<string>();
			StringBuilder builder = new StringBuilder("");

			foreach (SlamCms.Data.Group group in groups)
			{
				if (keywords.Any(k => group.Name.Contains(k)) && group.SiteCollectionGroupId != 0)
				{
					if (group.Sites != null)
					{
						var site = group.Sites.FirstOrDefault();
						if (site != null)
						{
							var zone = site.Zones.FirstOrDefault();

							string groupText = (zone != null ? (zone.Title + ":") : String.Empty) + group.Name; ;
							if (!convertedGroups.Contains(groupText))
							{
								builder.Append(groupText + ";");
								convertedGroups.Add(groupText);
							}
						}
					}
					else
					{
						if (!convertedGroups.Contains(group.Name))
						{
							builder.Append(group.Name + ";");
							convertedGroups.Add(group.Name);
						}
					}
				}
			}
			return convertedGroups.Count() > 0 ? String.Join(";", convertedGroups.OrderBy(o => o).ToArray()) : String.Empty;
		}

		private string ConvertTagsToString(ICollection<SlamCms.Data.Tag> tags)
		{
			StringBuilder builder = new StringBuilder("");
			foreach (SlamCms.Data.Tag t in tags)
			{
				builder.Append(t.DisplayName + ";");
			}

			return builder.Length > 0 ? builder.Remove(builder.Length - 1, 1).ToString() : builder.ToString();
		}
	}
}